/* vcc/vcc.c 
 * 
 * This file is part of vcc. 
 * 
 * vcc is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vcc is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vcc. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <signal.h>
#include <netdb.h>

#include <klist.h>
#include <vcc/vcc.h>
#include <vcc/plugin.h>
#include <vcc/version.h>
#include <vcc/interfaces.h>

#define clear_req(req) 	memset(req, 0, sizeof(*req))

int current_sid = 0;
struct klist_node sname_cache = KLIST_NODE_INIT(&sname_cache);


int init_socket(const char *server_addr, int server_port) {
	struct sockaddr_in addr, server;
	struct hostent *hentry;

	if (unlikely((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)) {
		perror("socket()");

		exit(1);
	}

	memset(&addr, 0, sizeof(struct sockaddr_in));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(0);

	if (unlikely(bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0)) {
		perror("bind()");

		exit(1);
	}

	memset(&server, 0, sizeof(struct sockaddr_in));

	server.sin_family = AF_INET;
	server.sin_port = htons(server_port);

	if (unlikely(!inet_aton(server_addr, &server.sin_addr))) {
		if (!(hentry = gethostbyname(server_addr))) {	
			fprintf(stderr, _("invalid address or host. "
				"leave blank if you want to connect the default server. \n"));

			exit(1);
		}

		inet_aton(inet_ntoa(*(struct in_addr *) hentry->h_addr), &server.sin_addr);
	}

	if (unlikely(connect(fd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) < 0)) {
		perror("connect()");

		exit(1);
	}

	return fd;
}



int __send_msg(const char *msg, const char *usrname, int flags) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_MSG_SEND);
	req.uid = 0;
	req.session = htonl(current_sid);
	req.flags = htonl(flags);
       
	strncpy(req.usrname, usrname, USRNAME_SIZE);
	strncpy(req.msg, msg, MSG_SIZE);
	write(fd, &req, REQ_SIZE);

	return 0;
}

extern inline int send_msg(const char *msg, const char *usrname) {
	return __send_msg(msg, usrname, 0);
}

extern inline int send_msg_encrypted(const char *msg, const char *usrname) {
	return __send_msg(msg, usrname, FLAG_ENCRYPTED);
}

int send_relay_msg(const char *msg, const char *usrname, const char *visible) {
	struct vcc_relay_header req;
	int len;

	len = strlen(msg);
	clear_req(&req);

	req.magic = htonl(VCC_MAGIC_RL);
	req.reqtype = htonl(REQ_REL_MSG);
	req.uid = 0;
	req.session = htonl(current_sid);
	req.size = htonl(sizeof(struct vcc_relay_header) + len);

	strncpy(req.usrname, usrname, USRNAME_SIZE);

	if (visible) 
		strncpy(req.visible, visible, USRNAME_SIZE);
	else 
		memset(req.visible, 0, USRNAME_SIZE);

	write(fd, &req, sizeof(struct vcc_relay_header));
	write(fd, msg, len);

	return 0;
}

int read_until(int fd, void *p, int until) {
	int did_read = 0;
	int n;

	while ((did_read += n = read(fd, p, until)) < until) {
		if (!n) 
			return 0;

		p += n;
	}
	
	return until;
}

char *relay_get_msg(int fd, struct vcc_relay_header *hdr) {
	char 	*p;
	int 	size;

	size = ntohl(hdr->size);

	if (unlikely(!(p = malloc(size)))) {
		fprintf(stderr, "*** malloc() failed\n");

		return NULL;
	}

	memset(p, 0, size);
	read_until(fd, p, size - sizeof(struct vcc_relay_header));

	return p;
}

int get_user_info(char *usrname) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_UINFO);
	req.uid = 0;

	strncpy(req.msg, usrname, USRNAME_SIZE);

	write(fd, &req, REQ_SIZE);
	
	return 0;
}

int get_users(void) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_USRS);
	req.uid = 0;

	write(fd, &req, REQ_SIZE);

	return 0;
}

int get_sessions(void) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_SESS);
	req.uid = 0;

	write(fd, &req, REQ_SIZE);

	return 0;
}


int do_bh(struct vcc_request *req) {
	switch (ntohl(req->reqtype)) {
	case REQ_CTL_LOGIN:
		vcc_login_bh(req);

		break;

#if 0
		/* obsolete, see do_relay_bh() */
	case REQ_CTL_USRS:
		do_cmd_ls_bh(req);

		break;
#endif

	case REQ_CTL_SESS:
		do_cmd_lsse_bh(req);

		break;

	case REQ_CTL_UINFO:
		do_cmd_uinfo_bh(req);

		break;

	case REQ_SYS_SCRINC:
		do_cmd_incr_bh(req);

		break;

	default:
		fprintf(stderr, "*** unexpected packet type %d. \n", ntohl(req->reqtype));

		break;
	}

	return 0;
}

int do_relay_bh(struct vcc_relay_header *req) {
	switch (ntohl(req->reqtype)) {
	case REQ_CTL_USRS:
		do_cmd_ls_bh(req);

		break;

	default:
		fprintf(stderr, "*** unexpected packet type %d. \n", ntohl(req->reqtype));

		break;
	}

	return 0;
}


int __login(char *usrname, char *passwd, int type) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);

	/* REQ_CTL_LOGIN or REQ_CTL_IALOG */
	req.reqtype = htonl(type);

	strncpy(req.usrname, usrname, USRNAME_SIZE);
	strncpy(req.msg, passwd, MSG_SIZE);

	write(fd, &req, REQ_SIZE);

	return 0;
}

int vcc_login(char *usrname, char *passwd) {
	return __login(usrname, passwd, REQ_CTL_LOGIN);
}

int ia_login(char *session) {
	return __login(usrname, session, REQ_CTL_IALOG);
}


int vcc_login_bh(struct vcc_request *req) {
	if (ntohl(req->reqtype) != REQ_CTL_LOGIN) {
		fprintf(stderr, "*** bad package. \n");

		return 1;
	}

	if (ntohl(req->uid)) 
		printf(_("ready. \n"));

	else {
		fprintf(stderr, _("login failed: wrong password or user doesn't exist. \n"));
		fprintf(stderr, _("if you haven't got an account on this server, ask the administrator " 
				"and you can get one. \n"));

		exit(1);
	}

	return 0;
}


int vcc_create_session(char *name) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_NEWSE);

	strncpy(req.usrname, name, USRNAME_SIZE);

	write(fd, &req, REQ_SIZE);

	return 0;
}


int join_session(int sid) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_JOINS);
	req.session = htonl(sid);

	write(fd, &req, REQ_SIZE);

	return 0;

}

int quit_session(int sid) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_QUITS);
	req.session = htonl(sid);

	write(fd, &req, REQ_SIZE);

	return 0;

}

int session_name(int sid, char *buf) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_CTL_SENAME);
	req.session = htonl(sid);

	write(fd, &req, REQ_SIZE);

	for (;;) {
		read(fd, &req, REQ_SIZE);

		if (ntohl(req.reqtype) != REQ_CTL_SENAME) 
			continue;

		if (ntohl(req.session) == -1u) 
			strcpy(buf, "*** no such session");

		else 
			strcpy(buf, req.msg);

		break;
	}

	return 0;
}

char *getsname(int sid) {
	struct klist_node *n;
	struct sname_cache *p;

	for (n = sname_cache.next; n != &sname_cache; n = n->next) {
		p = sname_cache_of(n);

		if (p->sid == sid) 
			return p->name;
	}

	if (unlikely(!(p = malloc(sizeof(struct sname_cache))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 0;
	}

	klist_init(&p->node);
	klist_add(&sname_cache, &p->node);

	session_name(sid, p->name);
	p->sid = sid;

	return p->name;
}

int get_name_sid(char *sname) {
	char *p;
	int i;

	for (i = 0; ; i++) {
		p = getsname(i);

		if (!strcmp(p, "*** no such session")) 
			return -1;

		if (!strcmp(sname, p)) 
			return i;
	}

	return -1;
}


int incr_score(char *name, int incr) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_SYS_SCRINC);
	req.session = htonl(incr);

	strcpy(req.usrname, name);
	write(fd, &req, REQ_SIZE);

	return 0;
}


int is_user_banned(char *usrname) {
	struct klist_node *item;
	struct banlist *ban;

	for (item = banned_people.next; item != &banned_people; item = item->next) {
		ban = banlist_of(item);

		if (ban->usrname && !strcmp(usrname, ban->usrname)) {
			return 1;
		}
	}
	return 0;
}


int get_sys_info(void) {
	struct vcc_request req;

	clear_req(&req);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_SYS_INFO);

	strcpy(req.usrname, usrname);
	write(fd, &req, REQ_SIZE);

	return 0;
}


